#include "common.h"


size_t SendBuffer(Conn *c, u_char *buf, size_t size)
{
    size_t       n;
    Event  *wev;

    wev = c->write;

    for ( ;; ) {
        n = write(c->fd, buf, size);
        if (n > 0) {
            if (n < (size_t) size) {
                wev->ready = 0;
            }
            c->sent += n;
            return n;
        }

        if (n == 0) {
            wev->ready = 0;
            return n;
        }

        if (errno == EAGAIN || errno == EINTR) {
            wev->ready = 0;
            if (errno == EAGAIN) {
                return NET_AGAIN;
            }

        } else {
            //wev->error = 1;
            return NET_ERR;
        }
    }
}


int OutputChain(Conn *c, Chain *in)
{
    off_t                      size, sent, nsent, limit;
    int                 last, flush;
    Chain               *cl, *ln, **ll, *chain;

    size = 0;
    flush = 0;
    last = 0;
    ll = &c->response->out;

    for (cl = c->response->out; cl; cl = cl->next) {
        ll = &cl->next;
        size += DS_BUF_SIZE(cl->buf);
    }

    /* add the new chain to the existent one */
    /* ln->buf->last != ln->buf->pos means this is an empty buf */
    for (ln = in; ln && ln->buf->last != ln->buf->pos; ln = ln->next) {
        cl = DS_AllocChainLink(c->pool);
        if (cl == NULL) {
            return DS_ERROR;
        }

        cl->buf = ln->buf;
        *ll = cl;
        ll = &cl->next;

        size += DS_BUF_SIZE(cl->buf);
    }

    *ll = NULL;

    sent = c->sent;

    chain = SendChain(c, c->response->out);

    if (chain == ((Chain*)-1)) {
        return DS_ERROR;
    }

    for (cl = c->response->out; cl && cl != chain; /* void */) {
        ln = cl;
        cl = cl->next;
        DS_FreeChain(c->pool, ln);
    }

    c->response->out = chain;

    if (chain) {
        return NET_AGAIN;
    }
    
    return NET_OK;
    
}

void WriteClientBuf(Event *event)
{
    int rc;
    Conn *c = event->data;
    ds_pool_s *pool = c->pool;

    rc = OutputChain(c, 0);
    if (rc == NET_OK) {
    	FinalizeConn(c);
    } else if (rc == NET_AGAIN) {
        EVENT_Handle_Write_Event(c->write, EVENT_CLEAR_EVENT);
    } else {
        FinalizeConn(c);
    }
}

Chain * SendChain(Conn *c, Chain *in)
{
    int            rc;
    off_t          size, send, prev_send, sent, fprev;
    u_char        *prev;
    size_t         file_size;
    Buf     *file;
    int     eintr, complete;
    ds_array_s header;
    Chain   *cl;
    struct iovec  *iov, headers[64];
    off_t          offset;

    send = 0;
    header.elts = headers;
    header.size = sizeof(struct iovec);
    header.nalloc = 64;
    header.pool = c->pool;

    for ( ;; ) {
        file = NULL;
        file_size = 0;
        eintr = 0;
        complete = 0;
        prev_send = send;

        header.nelts = 0;

        prev = NULL;
        iov = NULL;

        /* create the iovec and coalesce the neighbouring bufs */
        for (cl = in; cl && header.nelts < 64; cl = cl->next)
        {
            if (cl->buf->in_file) {
                break;
            }
            size = cl->buf->last - cl->buf->pos;
            if (prev == cl->buf->pos) {
                iov->iov_len += (size_t) size;

            } else {
                iov = DS_ArrayPush(&header);
                if (iov == NULL) {
                    return (Chain*)DS_ERROR;
                }
                iov->iov_base = (void *) cl->buf->pos;
                iov->iov_len = (size_t) size;
            }
            prev = cl->buf->pos + (size_t) size;
            send += size;
            /* send stores the neighbouring bufs total size */
            /* prev stores the current last buf pos */
        }

        if (header.nelts == 0 && cl && cl->buf->in_file) {
            file = cl->buf;

            /* coalesce the neighbouring file bufs */
            do {
                size = cl->buf->file_last - cl->buf->file_pos;
                file_size += (size_t) size;
                send += size;
                fprev = cl->buf->file_pos + size;
                cl = cl->next;

            } while (cl && cl->buf->in_file
                     && file->fileDes == cl->buf->fileDes
                     && fprev == cl->buf->file_pos);
        }

        if (file) {

            if (file_size == 0) {
                return (Chain*)DS_ERROR;
            }
            offset = file->file_pos;
            rc = sendfile(c->fd, file->fileDes, &offset, file_size);
            if (rc == -1) {
                switch (errno) {
                case EAGAIN:
                    break;

                case EINTR:
                    eintr = 1;
                    break;
                default:
                    return (Chain*)DS_ERROR;
                }
            }
            sent = rc > 0 ? rc : 0;
        } else {
            rc = writev(c->fd, header.elts, header.nelts);

            if (rc == -1) {
                switch (errno) {
                case EAGAIN:
                    break;
                case EINTR:
                    eintr = 1;
                    break;
                default:
                    return (Chain*)DS_ERROR;
                }
            }
            sent = rc > 0 ? rc : 0;
        }

        if (send - prev_send == sent) {
            complete = 1;
        }

        c->sent += sent;

        for (cl = in; cl; cl = cl->next) {

            if (sent == 0) {
                break;
            }

            size = DS_BUF_SIZE(cl->buf);
                            
            if (sent >= size) {
                sent -= size;

                if (!cl->buf->in_file) {
                    cl->buf->pos = cl->buf->last;
                } else {
                    cl->buf->file_pos = cl->buf->file_last;
                    close(cl->buf->fileDes);
                }
                continue;
            }

            if (!cl->buf->in_file) {
                cl->buf->pos += (size_t) sent;
            } else {
                cl->buf->file_pos += sent;
            }
            break;
        }

        if (eintr) {
            continue;
        }

        if (!complete) {
            return cl;
        }

        if (cl == NULL) {
            return cl;
        }
        in = cl;
    }
    
}

